// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 此文件实现类型参数推断。

package types2

import (
	"bytes"
	"cmd/compile/internal/syntax"
	"fmt"
)

const useConstraintTypeInference = true

// 推断尝试根据给定的类型参数tparams、类型参数targets、函数参数params和
// 的完整类型参数集。必须至少有一个类型参数，类型参数
// 函数参数args（如果有），推断泛型函数实例化/调用
// 不得超过类型参数，参数和参数的数量必须匹配（包括零）。
// 如果成功，推断将返回类型参数的完整列表，每个类型参数一个。
// 否则结果为零，将报告相应的错误。
// 
// 推理过程如下：
// 
// 从给定的类型参数开始
// 1）使用类型化参数应用FTI（函数类型推理），
// 2）应用CTI（约束类型推理），
// 3）使用非类型化函数参数应用FTI，
// 4）应用CTI。
// 
// 当所有类型参数已知或出现错误时，进程立即停止。
func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand) (result []Type) {
	if debug {
		defer func() {
			assert(result == nil || len(result) == len(tparams))
			for _, targ := range result {
				assert(targ != nil)
			}
			// 检查。dump（“###推断的目标=%s”，result）
		}()
	}

	if traceInference {
		check.dump("-- inferA %s%s ➞ %s", tparams, params, targs)
		defer func() {
			check.dump("=> inferA %s ➞ %s", tparams, result)
		}()
	}

	// 必须至少有一个类型参数，并且类型参数不能多于类型参数。
	n := len(tparams)
	assert(n > 0 && len(targs) <= n)

	// 函数参数和参数的数量必须匹配。
	assert(params.Len() == len(args))

	// 如果我们已经有了所有类型参数，那么我们就完成了。
	if len(targs) == n {
		return targs
	}
	// len（targets）<n 

	const enableTparamRenaming = true
	if enableTparamRenaming {
		// 为了进行类型推断，我们必须将显式类型或值函数参数中出现的类型参数
		// 与我们通过统一求解的类型
		// 参数区分开来，因为它们在自递归调用中可能是相同的
		// 参数。例如：
		// 
		// func f[P*Q，Q any]（P，Q Q）{
		// f（P）
		// }
		// 
		// ，实例f[P]中使用的P与我们试图通过
		// 统一来解决的P具有相同的指针标识，这一事实是巧合的：递归
		// 调用没有什么特别之处，会导致它们将类型参数的标识
		// 与类型参数混为一谈。换句话说：任何这样的自递归
		// 调用都相当于一个相互递归的调用，它不会遇到
		// 任何类型参数标识的问题。例如，下面的代码
		// 等同于上面的代码。
		// 
		// func f[P interface{*Q}，qany]（P，qq）{
		// f2（P）
		// }
		// 
		// func f2[P interface{*Q}，qany P，qq）{
		// f（P）
		// 原始签名中的参数，以给它们一个新身份。作为一个
		// 优化，我们只对自递归调用执行此操作。

		// 我们可以通过比较当前函数中第一个类型参数的
		// 标识和tparams中第一个类型参数的
		// 来检测我们是否处于自递归调用中。这是因为类型参数对其类型参数列表是唯一的。
		selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0)

		if selfRecursive {
			// 在自递归推理中，将类型参数重命名为新类型
			// 相同但具有指针标识的参数。
			tparams2 := make([]*TypeParam, len(tparams))
			for i, tparam := range tparams {
				tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
				tparams2[i] = NewTypeParam(tname, nil)
				tparams2[i].index = tparam.index // /==i 
			}

			renameMap := makeRenameMap(tparams, tparams2)
			for i, tparam := range tparams {
				tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil)
			}

			tparams = tparams2
			params = check.subst(pos, params, renameMap, nil).(*Tuple)
		}
	}

	// 如果我们有两个以上的参数，我们可能有命名类型和未命名类型的参数。
	// 如果是这种情况，请对参数和参数进行排列，使具有命名
	// 类型的参数位于列表的第一位。如果所有类型都按原样使用
	// 这不会影响类型推断。但是，当我们启用了不精确的统一时（就像函数类型
	// 推理的情况一样），当命名类型与未命名类型统一时，统一将与命名类型的基础类型进行
	// 统一，因为否则统一将立即失败。这导致了类型推断的不对称性：如果命名类型和未命名类型的参数被传递给具有相同类型的参数，则可以根据参数的顺序推断不同类型的
	// （命名与基础）。
	// 通过确保首先看到命名类型，避免了顺序依赖，并且统一
	// 尽可能地成功。
	// 
	// 这段代码目前处于禁用状态，尚待决定我们是否要解决类似
	// 这样的情况，并使类型推断的规范更加复杂（参见问题#43056）。
	const enableArgSorting = false
	if m := len(args); m >= 2 && enableArgSorting {
		// 确定具有命名和未命名类型的参数的索引。
		var named, unnamed []int
		for i, arg := range args {
			if hasName(arg.typ) {
				named = append(named, i)
			} else {
				unnamed = append(unnamed, i)
			}
		}

		// 如果我们有命名类型和未命名类型，请先用
		// 命名类型移动参数。相应地更新参数列表。
		// 复制，以免撞击传入的切片。
		if len(named) != 0 && len(unnamed) != 0 {
			params2 := make([]*Var, m)
			args2 := make([]*operand, m)
			i := 0
			for _, j := range named {
				params2[i] = params.At(j)
				args2[i] = args[j]
				i++
			}
			for _, j := range unnamed {
				params2[i] = params.At(j)
				args2[i] = args[j]
				i++
			}
			params = NewTuple(params2...)
			args = args2
		}
	}

	// /--1---
	// 继续使用我们的类型参数。避免将已有类型参数的泛型参数与函数参数进行匹配：
	// 可能会失败，因为匹配使用类型标识，而传递
	// 的参数使用赋值规则。用
	// 类型实例化参数列表，然后继续使用该参数列表。

	// 首先，确保我们有一个类型参数的“完整”列表，其中一些
	// 可能为零（未知）。制作一份副本，以免撞击进入的切片。
	if len(targs) < n {
		targs2 := make([]Type, n)
		copy(targs2, targs)
		targs = targs2
	}
	// len（targets）==n 

	// 用类型参数替换参数中各自的类型参数，
	// 如果有。请注意，检查会忽略nil targs条目。subst.
	// TODO（gri）我们可以避免这种情况吗（我们正在下面设置已知类型参数，
	// 但这暂时不会影响isParameterized检查）。
	if params.Len() > 0 {
		smap := makeSubstMap(tparams, targs)
		params = check.subst(nopos, params, smap, nil).(*Tuple)
	}

	// 统一带有类型化参数的泛型参数的参数和参数类型
	// 并收集带有非类型化参数的泛型参数的索引。
	// 术语：泛型参数=类型参数化类型的函数参数
	u := newUnifier(false)
	u.x.init(tparams)

	// 设置我们已经知道的类型参数。
	for i, targ := range targs {
		if targ != nil {
			u.x.set(i, targ)
		}
	}

	errorf := func(kind string, tpar, targ Type, arg *operand) {
		// 如果我们可以提供更好的错误消息
		targs, index := u.x.types()
		if index == 0 {
			// 第一个类型参数无法推断。
			// 如果它们都无法推断，请不要尝试在错误消息中提供推断类型。ABCFDG 
			allFailed := true
			for _, targ := range targs {
				if targ != nil {
					allFailed = false
					break
				}
			}
			if allFailed {
				check.errorf(arg, "%s %s of %s does not match %s (cannot infer %s)", kind, targ, arg.expr, tpar, typeParamsString(tparams))
				return
			}
		}
		smap := makeSubstMap(tparams, targs)
		inferred := check.subst(arg.Pos(), tpar, smap, nil)
		if inferred != tpar {
			check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar)
		} else {
			check.errorf(arg, "%s %s of %s does not match %s", kind, targ, arg.expr, tpar)
		}
	}

	var indices []int
	for i, arg := range args {
		par := params.At(i)
		// 泛型函数（我们想推断它的类型参数），因此typ没有参数化。
		if isParameterized(tparams, par.typ) {
			if arg.mode == invalid {
				// 之前报告了一个错误。忽略此targ 
				// 继续，我们可能仍然能够推断出所有
				// targ，从而减少后续错误。
				continue
			}
			if targ := arg.typ; isTyped(targ) {
				// 如果我们允许双向统一，并且targ是
				// 一个泛型函数，我们需要用
				// targ的相应类型参数初始化u.y。
				if !u.unify(par.typ, targ) {
					errorf("type", par.typ, targ, arg)
					return nil
				}
			} else if _, ok := par.typ.(*TypeParam); ok {
				// 由于默认类型都是基本（即非复合）类型，
				// 非类型化参数永远不会与复合参数类型匹配；
				// 它可能匹配的唯一参数类型是*TypeParam。
				// 因此，对于非类型参数，我们只需要查看参数类型
				// 它们是单类型参数。
				indices = append(indices, i)
			}
		}
	}

	// 如果我们有了所有类型参数，我们就完成了。
	var index int
	targs, index = u.x.types()
	if index < 0 {
		return targs
	}

	// /--2---
	// 看看我们在约束类型推断方面取得了多大进展。
	// 注意，即使我们没有任何类型参数，约束类型推断
	// 也可能会为显式指定类型的约束生成结果。
	if useConstraintTypeInference {
		targs, index = check.inferB(pos, tparams, targs)
		if targs == nil || index < 0 {
			return targs
		}
	}

	// /--3---
	// 使用任何非类型化参数来推断其他类型参数。
	// 一些带有非类型化参数的泛型参数可能已经被赋予了
	// 类型，我们可以忽略它们。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊
	for _, i := range indices {
		if targs[tpar.index] == nil {
			arg := args[i]
			targ := Default(arg.typ)
			// 非类型化nil的默认类型为非类型化nil。我们不能将非类型化的nil类型推断为类型参数类型。通过确保所有默认参数类型都已键入，忽略未键入的
			// nil。
			if isTyped(targ) && !u.unify(tpar, targ) {
				errorf("default type", tpar, targ, arg)
				return nil
			}
		}
	}

	// 如果我们有所有类型参数，我们就完成了。
	targs, index = u.x.types()
	if index < 0 {
		return targs
	}

	// /--4---
	// 再次，继续进行约束类型推断。
	if useConstraintTypeInference {
		targs, index = check.inferB(pos, tparams, targs)
		if targs == nil || index < 0 {
			return targs
		}
	}

	// 至少无法推断一个类型参数。
	assert(targs != nil && index >= 0 && targs[index] == nil)
	tpar := tparams[index]
	check.errorf(pos, "cannot infer %s (%s)", tpar.obj.name, tpar.obj.pos)
	return nil
}

// typeParamsString生成一个类型参数名字符串
// 在适合人类消费的清单中。
func typeParamsString(list []*TypeParam) string {
	// 常见情况
	n := len(list)
	switch n {
	case 0:
		return ""
	case 1:
		return list[0].obj.name
	case 2:
		return list[0].obj.name + " and " + list[1].obj.name
	}

	// 一般情况（n>2）
	// 希望使用字符串。但它在Go 1.4中不可用。
	var b bytes.Buffer
	for i, tname := range list[:n-1] {
		if i > 0 {
			b.WriteString(", ")
		}
		b.WriteString(tname.obj.name)
	}
	b.WriteString(", and ")
	b.WriteString(list[n-1].obj.name)
	return b.String()
}

// IsParameterized报告typ是否包含tparams的任何类型参数。
func isParameterized(tparams []*TypeParam, typ Type) bool {
	w := tpWalker{
		seen:    make(map[Type]bool),
		tparams: tparams,
	}
	return w.isParameterized(typ)
}

type tpWalker struct {
	seen    map[Type]bool
	tparams []*TypeParam
}

func (w *tpWalker) isParameterized(typ Type) (res bool) {
	// 检测周期
	if x, ok := w.seen[typ]; ok {
		return x
	}
	w.seen[typ] = false
	defer func() {
		w.seen[typ] = res
	}()

	switch t := typ.(type) {
	case nil, *Basic: // TODO（gri）是否应在此处处理零？
		break

	case *Array:
		return w.isParameterized(t.elem)

	case *Slice:
		return w.isParameterized(t.elem)

	case *Struct:
		for _, fld := range t.fields {
			if w.isParameterized(fld.typ) {
				return true
			}
		}

	case *Pointer:
		return w.isParameterized(t.base)

	case *Tuple:
		n := t.Len()
		for i := 0; i < n; i++ {
			if w.isParameterized(t.At(i).typ) {
				return true
			}
		}

	case *Signature:
		// t.t如果我们查看的是一个通用函数类型（或接口方法）的签名
		// ，它是我们正在测试的类型的一部分，那么它可能不是零。我们不关心这些类型的
		// 参数。
		// 类似地，方法的接收者可以声明（而不是
		// 使用）类型参数，我们也不关心这些参数。
		// 因此，我们只需要查看输入和结果参数。
		return w.isParameterized(t.params) || w.isParameterized(t.results)

	case *Interface:
		tset := t.typeSet()
		for _, m := range tset.methods {
			if w.isParameterized(m.typ) {
				return true
			}
		}
		return tset.is(func(t *term) bool {
			return t != nil && w.isParameterized(t.typ)
		})

	case *Map:
		return w.isParameterized(t.key) || w.isParameterized(t.elem)

	case *Chan:
		return w.isParameterized(t.elem)

	case *Named:
		return w.isParameterizedTypeList(t.targs.list())

	case *TypeParam:
		// t必须是w.tparams 
		return tparamIndex(w.tparams, t) >= 0

	default:
		unreachable()
	}

	return false
}

func (w *tpWalker) isParameterizedTypeList(list []Type) bool {
	for _, t := range list {
		if w.isParameterized(t) {
			return true
		}
	}
	return false
}

// inferB返回从类型参数的
// 边界推断出的实际类型参数列表和一组初始类型参数。如果由于
// 统一失败而无法进行类型推断，则如果report设置为true，则会报告一个错误，生成的类型
// 列表为零，索引为0。
// 否则，types是推断类型参数的列表，index是该列表中无法推断的第一个类型参数的索引（因此为零）。如果成功推断出所有
// 类型参数，则索引<0。提供的类型参数
// 的数量可能小于类型参数的数量，但必须至少有一个。
func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) (types []Type, index int) {
	assert(len(tparams) >= len(targs) && len(targs) > 0)

	if traceInference {
		check.dump("-- inferB %s ➞ %s", tparams, targs)
		defer func() {
			check.dump("=> inferB %s ➞ %s", tparams, types)
		}()
	}

	// 在约束
	// 和相应的类型参数（可能为零！）之间设置双向统一。
	u := newUnifier(false)
	u.x.init(tparams)
	u.y = u.x // 统一的LHS和RHS之间的类型参数是相同的

	// 设置我们已经知道的类型参数。
	for i, targ := range targs {
		if targ != nil {
			u.x.set(i, targ)
		}
	}

	// 只要
	// 仍然存在未知的类型参数，并且正在取得进展，就重复应用约束类型推断。
	// 
	// 这是一个O（n^2）算法，其中n是
	// 类型参数的数量：如果有进展（迭代
	// 继续），则每次迭代至少会推断出一个类型参数
	// 并且我们有一个双嵌套循环。
	// 实际上这不是问题，因为类型参数的数量往往非常小（<5个左右）。
	// （统一应该可以有效地向新推断的类型参数发送信号；然后循环
	// 这里只能处理各自的类型参数
	// 但这将以额外的复杂性为代价，而
	// 可能不值得）
	for n := u.x.unknowns(); n > 0; {
		nn := n

		for i, tpar := range tparams {
			// 如果有一个核心术语（即，一个具有波浪号信息的核心类型），
			// 将类型参数与核心类型统一起来。
			if core, single := coreTerm(tpar); core != nil {
				// 一个类型参数可以在两种情况下与其核心类型统一。
				tx := u.x.at(i)
				switch {
				case tx != nil:
					// 相应的类型参数tx已知。
					// 在本例中，如果核心类型有一个波浪号，则类型参数的基础
					// 类型必须与核心类型匹配，否则类型参数和核心类型
					// 必须匹配。ABCFDG 
					// 核心进行统一。类型。
					// 还要注意的是，即使是不精确的统一，我们也不能离开
					// 下面的调用，因为tx和core都有可能。typ是命名类型，
					// 其中under（tx）是一个（命名的）基本类型匹配核心。类型。这种情况与不准确的统一不符。
					if core.tilde && !isTypeParam(tx) {
						tx = under(tx)
					}
					if !u.unify(tx, core.typ) {
						// TODO（gri）通过提供类型参数来改进错误消息
						// 我们已经知道
						// 不要使用术语。String（），因为它总是限定类型，即使它们在当前包中。
						tilde := ""
						if core.tilde {
							tilde = "~"
						}
						check.errorf(pos, "%s does not match %s%s", tpar, tilde, core.typ)
						return nil, 0
					}

				case single && !core.tilde:
					// 对应的类型参数tx未知，并且只有一个
					// 特定类型且没有波浪号。
					// 在本例中，类型参数必须是该单一类型；设置它。
					u.x.set(i, core.typ)

				default:
					// 统一是不可能的，也没有取得任何进展。
					continue
				}

				// 已知类型参数的数量可能已更改。
				nn = u.x.unknowns()
				if nn == 0 {
					break // 所有类型参数都是已知的
				}
			}
		}

		assert(nn <= n)
		if nn == n {
			break // 没有进展
		}
		n = nn
	}

	// u.x.types（）现在包含传入类型参数和任何其他类型
	// 从核心术语推断出的论点。新推断出的非nil 
	// 条目可能仍然包含对其他类型参数的引用。
	// 例如，对于[A any，B interface{[]C}，C interface{*A}]，如果给定A==int 
	// 统一生成类型列表[int，[]C，*A]。我们通过替换此类型列表中的类型参数来消除剩余的类型参数
	// 直到不再有任何更改。
	types, _ = u.x.types()
	if debug {
		for i, targ := range targs {
			assert(targ == nil || types[i] == targ)
		}
	}

	// 每个（提供或推断的）类型的数据结构表示一个图，其中
	// 每个节点对应一个类型，每个（定向的）vertice指向一个组件
	// 类型。上面描述的替换过程重复地将这些图中的类型参数
	// 节点替换为类型参数所代表的类型的图
	// 这将为每种类型创建一个新的（可能更大的）图。
	// 如果类型参数
	// 的替换图也包含该类型参数，则替换过程不会停止。
	// 例如，对于[A接口{*A}]，没有为A提供任何类型参数，
	// 统一生成类型列表[*A]。用
	// A的值替换*A中的A将通过产生[**A]、
	// 因为图A->*A有一个通过A的循环。
	// 通常，循环可以发生在多个类型参数和推断类型ABCFDG 
	// 我们通过遍历所有类型参数的图来消除循环。如果通过类型参数检测到一个循环
	// 则cycleFinder将相应的
	// 类型置零，从而终止该循环；这也意味着各自的类型不能为
	// 推断。
	// 
	// TODO（gri）如果有用，我们可以将各自的周期报告为错误。我们现在不这样做，因为类型推断无论如何都会失败，而且，
	// 具有此类循环的约束目前不能由
	// 任何用户提供的类型来满足。但如果这种情况发生变化，报告错误
	// 将是错误的。
	w := cycleFinder{tparams, types, make(map[Type]bool)}
	for _, t := range tparams {
		w.typ(t) // t！=nil 
	}

	// 脏跟踪可能仍包含类型参数的所有类型的索引。
	// 我们知道，nil类型的条目和对应于提供的（非nil）
	// 类型参数的条目是干净的，所以从一开始就排除它们。
	var dirty []int
	for i, typ := range types {
		if typ != nil && (i >= len(targs) || targs[i] == nil) {
			dirty = append(dirty, i)
		}
	}

	for len(dirty) > 0 {
		// TODO（gri）不是为每次迭代创建一个新的子映射，而是为子映射提供一个更新操作，仅在需要时更改。优化。
		smap := makeSubstMap(tparams, types)
		n := 0
		for _, index := range dirty {
			t0 := types[index]
			if t1 := check.subst(nopos, t0, smap, nil); t1 != t0 {
				types[index] = t1
				dirty[n] = index
				n++
			}
		}
		dirty = dirty[:n]
	}

	// 一旦不再有任何更改，我们可能仍然保留类型参数；
	// 例如，核心类型为*P的约束可能与类型参数Q匹配，但
	// 我们没有任何类型参数来填充*P或Q（问题#45548）。
	// 不要让这样的推论逃脱，相反，要把它们置零。
	for i, typ := range types {
		if typ != nil && isParameterized(tparams, typ) {
			types[i] = nil
		}
	}

	// 更新索引
	index = -1
	for i, typ := range types {
		if typ == nil {
			index = i
			break
		}
	}

	return
}

// 如果type参数有一个特定的类型S，coreTerm将返回（S，true）。
// 否则，如果tpar有一个核心类型T，它将返回一个与该
// 核心类型对应的术语，并返回false。在这种情况下，如果tpar的任何术语有一个波浪号，则核心
// 术语有一个波浪号。在所有其他情况下，coreTerm返回（零，假）。
func coreTerm(tpar *TypeParam) (*term, bool) {
	n := 0
	var single *term // 如果n==1则有效
	var tilde bool
	tpar.is(func(t *term) bool {
		if t == nil {
			assert(n == 0)
			return false // 没有术语
		}
		n++
		single = t
		if t.tilde {
			tilde = true
		}
		return true
	})
	if n == 1 {
		if debug {
			assert(debug && under(single.typ) == coreType(tpar))
		}
		return single, true
	}
	if typ := coreType(tpar); typ != nil {
		// 核心类型始终是基础类型。
		// 如果tpar的任何术语有一个波浪号，我们没有一个精确的核心类型，我们必须返回
		// 一个波浪号。
		return &term{tilde, typ}, false
	}
	return nil, false
}

type cycleFinder struct {
	tparams []*TypeParam
	types   []Type
	seen    map[Type]bool
}

func (w *cycleFinder) typ(typ Type) {
	if w.seen[typ] {
		// 我们以前见过典型。如果它是tparams中的类型参数
		// 之一，则迭代替换将导致无限扩展。
		// 清除有效终止循环的相应类型。
		if tpar, _ := typ.(*TypeParam); tpar != nil {
			if i := tparamIndex(w.tparams, tpar); i >= 0 {
				// 循环tpar 
				w.types[i] = nil
			}
		}
		// 如果我们没有一个类型参数，循环是由于
		// 是一个普通的递归类型，我们可以停止遍历它。
		return
	}
	w.seen[typ] = true
	defer delete(w.seen, typ)

	switch t := typ.(type) {
	case *Basic:
		// 无需执行

	case *Array:
		w.typ(t.elem)

	case *Slice:
		w.typ(t.elem)

	case *Struct:
		w.varList(t.fields)

	case *Pointer:
		w.typ(t.base)

	// 大小写*元组：
	// 不应出现这种情况，因为元组只出现在显式处理的签名中。

	case *Signature:
		if t.params != nil {
			w.varList(t.params.vars)
		}
		if t.results != nil {
			w.varList(t.results.vars)
		}

	case *Union:
		for _, t := range t.terms {
			w.typ(t.typ)
		}

	case *Interface:
		for _, m := range t.methods {
			w.typ(m.typ)
		}
		for _, t := range t.embeddeds {
			w.typ(t)
		}

	case *Map:
		w.typ(t.key)
		w.typ(t.elem)

	case *Chan:
		w.typ(t.elem)

	case *Named:
		for _, tpar := range t.TypeArgs().list() {
			w.typ(tpar)
		}

	case *TypeParam:
		if i := tparamIndex(w.tparams, t); i >= 0 && w.types[i] != nil {
			w.typ(w.types[i])
		}

	default:
		panic(fmt.Sprintf("unexpected %T", typ))
	}
}

func (w *cycleFinder) varList(list []*Var) {
	for _, v := range list {
		w.typ(v.typ)
	}
}
